﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Collections;
using Parser.Helpers;

namespace Parser
{
    /// <summary>
    /// Represents a log file parser that accepts Microsoft W3C protocol.
    /// </summary>
    /// 
    /// <author>Alexander Seeliger </author>
    public class IISHTTPParser
    {
        #region private variables 

        /// <summary>
        /// Path to log file
        /// </summary>
        private string logFile = "";

        /// <summary>
        /// Site name to identify the log file
        /// </summary>
        private string siteName = "";

        /// <summary>
        /// Beginning of parse process
        /// </summary>
        private long lineStart = 0;

        /// <summary>
        /// Current line of the parse process
        /// </summary>
        private long currentLine = 0;

        /// <summary>
        /// Table that contains the current log file
        /// </summary>
        private DataTable logTable;

        /// <summary>
        /// Log file fields
        /// </summary>
        private string[] fields;

        /// <summary>
        /// The page urls that have been identified
        /// </summary>
        private Dictionary<string, PageProperties> pages;

        /// <summary>
        /// Represents the user visits with detail usage
        /// </summary>
        private Dictionary<string, Visitor> visits;

        #endregion

        #region public properties

        /// <summary>
        /// Sets or returns the current line of the parce process
        /// </summary>
        public long CurrentLine
        {
            get
            {
                return currentLine;
            }
            set
            {
                currentLine = value;
            }
        }

        /// <summary>
        /// Returns or sets the page urls
        /// </summary>
        public Dictionary<string, PageProperties> Pages
        {
            get 
            { 
                return pages; 
            }
            set 
            { 
                pages = value; 
            }
        }

        /// <summary>
        /// Returns or sets the user visits with detail usage
        /// </summary>
        public Dictionary<string, Visitor> Visits
        {
            get 
            { 
                return visits; 
            }
            set 
            { 
                visits = value; 
            }
        }

        #endregion

        #region constructors

        /// <summary>
        /// Constructs a new log file parser with the given log file
        /// </summary>
        /// <param name="LogFile">Full qualified path to the log file</param>
        public IISHTTPParser(string LogFile)
        {
            // check if the file exists
            if (!File.Exists(LogFile))
            {
                throw new FileNotFoundException("File could not be found.", LogFile);
            }

            // set the current log file
            this.logFile = LogFile;
        }

        #endregion

        #region public functions

        /// <summary>
        /// Parse the file and calculate all important stuff
        /// </summary>
        /// <param name="name">Site name</param>
        /// <param name="Month">Month (1-12) of the year</param>
        /// <param name="Year">Year</param>
        public void Parse(string SiteName, long LineStart)
        {
            this.siteName = SiteName;
            this.lineStart = LineStart;

            // initalize the datatable on which the parser works
            logTable = new DataTable();
            Pages = new Dictionary<string, PageProperties>();
            visits = new Dictionary<string, Visitor>();

            // read logfile
            using (FileStream fs = new FileStream(logFile, FileMode.Open, FileAccess.Read))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    // temporary variable to store the current line
                    string currentLine;

                    // read line for line and parse
                    while (!sr.EndOfStream)
                    {
                        // read next line
                        currentLine = sr.ReadLine();

                        // parse the current line
                        ParseLine(currentLine);
                    }
                }
            }

            // calculate http traffic
            CreateSiteStatistics();
        }

        #endregion

        #region private functions

        /// <summary>
        /// Parses a single line
        /// </summary>
        /// <param name="text">Line to be parsed</param>
        private void ParseLine(string text)
        {
            // trim the line
            text = text.Trim();
            currentLine += 1;

            // check comments
            if (text.StartsWith("#"))
            {
                // fields
                if (text.Contains("#Fields:") && logTable.Columns.Count <= 0)
                {
                    ParseFields(text);
                }
            }
            else
            {
                // only parse the lines that are new
                if (lineStart < currentLine)
                {
                    // data row
                    string[] datarow = Regex.Split(text, "\\s");
                    try
                    {
                        logTable.Rows.Add(datarow);
                    }
                    catch
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Parse the fields
        /// </summary>
        /// <param name="text">Line that contains the #Fields</param>
        private void ParseFields(string text)
        {
            // split the string
            string[] fields = text.Replace("#Fields: ", "").Split(' ');
            this.fields = fields;

            // add the columns
            for (int i = 0; i < fields.Length; i++)
            {
                // add the fields to the row
                logTable.Columns.Add(fields[i]);
            }
        }

        /// <summary>
        /// Create the site statistics
        /// </summary>
        private void CreateSiteStatistics()
        {
            DataRow[] rows = logTable.Select("[cs-version] LIKE '%HTTP%' AND [s-sitename] LIKE '" + siteName + "'");

            foreach (DataRow row in rows)
            {
                // add page?
                PageProperties page;

                if (pages.ContainsKey(row["cs-uri-stem"].ToString()))
                {
                    // refresh page
                    page = pages[row["cs-uri-stem"].ToString()];

                    page.AverageTimeTaken = (page.AverageTimeTaken + int.Parse(row["time-taken"].ToString())) / 2;
                    page.AverageTraffic = (page.AverageTraffic + int.Parse(row["sc-bytes"].ToString())) / 2;
                    page.SentTraffic += long.Parse(row["sc-bytes"].ToString());
                    page.Hits += 1;
                }
                else
                {
                    // add page
                    page = new PageProperties();

                    page.AverageTimeTaken = int.Parse(row["time-taken"].ToString());
                    page.AverageTraffic = long.Parse(row["sc-bytes"].ToString());
                    page.SentTraffic = long.Parse(row["sc-bytes"].ToString());
                    page.Status = row["sc-status"].ToString();
                    page.Hits = 1;

                    pages.Add(row["cs-uri-stem"].ToString(), page);
                }

                // add user visit
                Visitor visit = new Visitor();
                if(visits.ContainsKey(row["c-ip"].ToString()))
                {
                    // user exists already
                    visit = visits[row["c-ip"].ToString()];
                    visit.VisitorTotalAction += 1;
                    visit.VisitorLastAction = DateTime.Parse(row["date"].ToString() + " " + row["time"].ToString());

                    if (SiteType.getSiteType(row["cs-uri-stem"].ToString()) == SiteType.SiteTypeEnum.Content)
                    {
                        visit.SiteVisits += 1;
                    }

                    visit.TrafficSent += long.Parse(row["sc-bytes"].ToString());
                    visit.TrafficGet += long.Parse(row["cs-bytes"].ToString());
                }
                else
	            {
                    // new user
                    visit = new Visitor();

                    visit.VisitorFirstAction = DateTime.Parse(row["date"].ToString() + " " + row["time"].ToString());
                    visit.VisitorLastAction = DateTime.Parse(row["date"].ToString() + " " + row["time"].ToString());
                    visit.VisitorTotalAction = 1;
                    visit.WebBrowser = row["cs(User-Agent)"].ToString();
                    visit.VisitorIp = row["c-ip"].ToString();
                    visit.Referer = row["cs(Referer)"].ToString();
                    visit.EntrySite = row["cs-uri-stem"].ToString();

                    // Parse user agent
                    UserAgent ua = new UserAgent(row["cs(User-Agent)"].ToString());
                    visit.OS = ua.OperationSystem;
                    visit.WebBrowser = ua.Webbrowser;
                    visit.WebBrowserVersion = ua.WebbrowserVersion;

                    if(SiteType.getSiteType(row["cs-uri-stem"].ToString()) == SiteType.SiteTypeEnum.Content)
                    {
                        visit.SiteVisits = 1;
                    }
                    
                    visit.VisitorType = Visitor.getVisitorType(row["cs(User-Agent)"].ToString());
                    visit.TrafficSent = long.Parse(row["sc-bytes"].ToString());
                    visit.TrafficGet = long.Parse(row["cs-bytes"].ToString());

                    visits.Add(row["c-ip"].ToString(), visit);
	            }
            }
        }

        #endregion
    }
}